package com.daiyang.lottery.service.mq;

import cn.hutool.core.date.DateUtil;
import com.daiyang.lottery.common.exception.ServiceException;
import com.daiyang.lottery.common.util.JacksonUtil;
import com.daiyang.lottery.common.util.MailUtil;
import com.daiyang.lottery.common.util.SMSUtil;
import com.daiyang.lottery.controller.param.DrawPrizeParam;
import com.daiyang.lottery.dao.dataobject.ActivityPrizeDO;
import com.daiyang.lottery.dao.dataobject.WinningRecordDO;
import com.daiyang.lottery.dao.mapper.ActivityPrizeMapper;
import com.daiyang.lottery.dao.mapper.WinningRecordMapper;
import com.daiyang.lottery.service.DrawPrizeService;
import com.daiyang.lottery.service.activitystatus.ActivityStatusManager;
import com.daiyang.lottery.service.dto.ConvertActivityStatusDTO;
import com.daiyang.lottery.service.enums.ActivityPrizeStatusEnum;
import com.daiyang.lottery.service.enums.ActivityPrizeTiersEnum;
import com.daiyang.lottery.service.enums.ActivityStatusEnum;
import com.daiyang.lottery.service.enums.ActivityUserStatusEnum;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.daiyang.lottery.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {
    private static final Logger logger = LoggerFactory.getLogger(MqReceiver.class);
    // 通知模板code
    private static final String WINNING_TEMPLATE_CODE = "SMS_465985911";
    @Autowired
    private DrawPrizeService drawPrizeService;
    @Autowired
    private ThreadPoolTaskExecutor asyncServiceExecutor;
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private ActivityStatusManager activityStatusManager;
    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @Autowired
    private ActivityPrizeMapper activiyPrizeMapper;
    @RabbitHandler
    public void  process (Map<String,String> message) throws Exception {


        //成功接收到队列消息
        logger.info("MQ成功接收到消息message={}",
                JacksonUtil.writeValueAsString(message));

        String paramString = message.get("messageDate");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);
      //  System.out.println("paramString="+paramString);
        //处理抽奖流程
        try{

            // 校验抽奖请求是否有效
            // 1、有可能前端发起两个一样的抽奖请求，对于param来说也是一样的两个请求
            // 2、param：最后一个奖项-》
            //      处理param1：活动完成、奖品完成
            //      处理param2: 回滚活动、奖品状态
            if( !drawPrizeService.checkDrawPrizeParam(param)) {
                return;
            }
            statusConvert(param);


            // 保存中奖者名单
            List<WinningRecordDO> winningRecordDOS
                    = drawPrizeService.saveWinnerRecords(param);

            // 通知中奖者（邮箱、短信）
            syncExecute(winningRecordDOS);
        }catch (ServiceException e){
            logger.error("处理 MQ 消息异常！{}:{}", e.getCode(), e.getMessage(), e);
            //需要保证事务一致性（回滚）
            rollback(param);
            throw e;

        }catch (Exception e){
            rollback(param);
            throw e;

        }


    }

    private void rollback(DrawPrizeParam param) {
        // 1、回滚状态：活动、奖品、人员
        // 状态是否需要回滚
        if (!statusNeedRollback(param)) {
            // 不需要：return
            return;
        }
        // 需要回滚: 回滚
        rollbackStatus(param);

        //2回滚中奖名单
        //是否需要回滚
        if(!winnrNeedRollback(param)){
            return;
        }
        // 需要
        rollbackWinner(param);

    }

    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }

    private boolean winnrNeedRollback(DrawPrizeParam param) {
        // 判断活动中的奖品是否存在中奖者
        int count =winningRecordMapper.countByAPId(param.getActivityId(),param.getPrizeId());

        return count>0;
    }

    private void rollbackStatus(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);
        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    private boolean statusNeedRollback(DrawPrizeParam param) {
        //判断活动+奖品+人员表相关状态是否已经扭转（正常思路）
        // 扭转状态时，保证了事务一致性，要么都扭转了，要么都没扭转（不包含活动）
        // 因此，只用判断人员/奖品是否扭转过，就能判断出状态是否全部扭转
        // 不能判断活动是否已经扭转
        // 结论：判断奖品状态是否扭转，就能判断出全部状态是否扭转
        ActivityPrizeDO activityPrizeDO =
                activiyPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());
        // 已经扭转了，需要回滚
        return activityPrizeDO.getStatus()
                .equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name());
    }

    private void syncExecute(List<WinningRecordDO> winningRecordDOS) {
        // execute不会抛异常影响主线程，所以这⾥不⽤抓异常
        // 抽奖之后的后续处理并发执⾏，后序可以采⽤⼀些设计模式，如策略模式
        asyncServiceExecutor.execute(() -> pushWinningList(winningRecordDOS));
       // asyncServiceExecutor.execute(() -> sendMessage(winningRecordDOS));
    }
    private void sendMessage(List<WinningRecordDO> recordDOList) {
        if(CollectionUtils.isEmpty(recordDOList)) {
            logger.warn("中奖名单为空！");
            return;
        }
        recordDOList.forEach(record -> {
            Map<String, String> templateParam = new HashMap<>();
            templateParam.put("name", record.getWinnerName());
            templateParam.put("activityName", record.getActivityName());
            templateParam.put("prizeTiers", ActivityPrizeStatusEnum.forName(
                    record.getPrizeTier()).getMessage());
            templateParam.put("prizeName", record.getPrizeName());
            templateParam.put("winningTime",
                    DateUtil.formatTime(record.getWinningTime()));
            smsUtil.sendMessage(WINNING_TEMPLATE_CODE,
                    record.getWinnerPhoneNumber().getValue(),
                    JacksonUtil.writeValueAsString(templateParam));
        });
    }

    private void pushWinningList(List<WinningRecordDO> recordDOList) {
        if(CollectionUtils.isEmpty(recordDOList)) {
            logger.warn("中奖名单为空！");
            return;
        }
        recordDOList.forEach(record -> {
            String context = "Hi，" + record.getWinnerName() + "。恭喜你在"
                    + record.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.formName(record.getPrizeTier()).getMessage()
                    + "：" + record.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(record.getWinningTime()) + "，请尽快领 取您的奖励！";
            mailUtil.sendSampleMail(record.getWinnerEmail(),
                    "中奖通知",
                    context);
        });
    }



    /**
     * 状态转换处理
     * @param param
     */
    private  void statusConvert(DrawPrizeParam param){
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);

        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);

        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);
        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }
//    private  void statusConvert(DrawPrizeParam param) {
//        // 问题
//        //1活动状态扭转有依赖性导致代码维护性差
//        //2 状态扭转条件可能会扩展，当前写法扩展性差，维护性差
//        //3 代码的灵活性扩展性，维护性差
//        //结局按方案：设计模式(责任链模式 策略模式)
//
//        //活动，RUNNING ->COMPLETE  全部奖品抽完之后才去改变
//        // 奖品， INIT ->COMPLETE
//        // 人员    INIT ->COMPLETE
//
//
//        //1 扭转奖品状态
//        //查询活动关联奖品信息
//        //条件判断是否符合扭转奖品状态，判断当前状态是否COMPLETE ,如果是不要扭转（其实在checkDrawPrizeParam中已经过滤过了）
//        //才去扭转
//        //2 扭转人员状态
//
//        //查活动关人员品信息
//        //条件判断是否符合扭转奖品状态，判断当前状态是否COMPLETE ,如果是不要扭转（其实在checkDrawPrizeParam中已经过滤过了）
//        //才去扭转
//
//        //3 扭转活动状态(必须再扭转奖品状态之后完成)
//        //查活动信息
//        //条件判断是否符合扭转奖品状态，判断当前状态是否COMPLETE ,如果是不要扭转（其实在checkDrawPrizeParam中已经过滤过了）
//        //才去扭转
//
//
//        //4 更新活动完整信息缓存
//
//    }
}
